Skip to content

Next Python SDK major - #5005

Draft
sentrivana wants to merge 414 commits into
masterfrom
major/3.0
Draft

sentrivana wants to merge 414 commits into
masterfrom
major/3.0

Conversation

@sentrivana

@sentrivana sentrivana commented Oct 24, 2025 •

Copy link
Copy Markdown
Contributor

We're preparing our next major on this branch.

The project is tracked in Linear. If you don't have access, we'll try to tag issues belonging to the project with the SDK 3.0 label on GitHub so that you can follow along.

Notable changes

  • Transaction-based tracing will be removed. Span streaming will be the default tracing model.
  • Python 3.6 support will be removed.

Context

You might have read this announcement about us discontinuing work on a 3.0. This is referring to the work done on the potel-base branch, which included two types of changes: a huge refactor of our tracing code on the one hand, and various unrelated changes, improvements and fixes on the other. We're dropping the huge refactor part, and only porting the rest, to a new branch and eventually a new 3.0 release.

@codecov

codecov Bot commented Oct 24, 2025 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.76%. Comparing base (14aff96) to head (d864ba0).
⚠️ Report is 4 commits behind head on master.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@             Coverage Diff             @@
##           master    #5005       +/-   ##
===========================================
+ Coverage   70.55%   83.76%   +13.21%     
===========================================
  Files         180      180               
  Lines       18077    18080        +3     
  Branches     3008     3009        +1     
===========================================
+ Hits        12754    15145     +2391     
+ Misses       4432     1943     -2489     
- Partials      891      992      +101     
Files with missing lines Coverage Δ
sentry_sdk/integrations/__init__.py 88.42% <100.00%> (+0.37%) ⬆️

... and 61 files with indirect coverage changes

@github-actions

github-actions Bot commented Mar 19, 2026 •

Copy link
Copy Markdown
Contributor

Codecov Results 📊

✅ 55565 passed | ⏭️ 2872 skipped | Total: 58437 | Pass Rate: 95.09% | Execution Time: 152m 59s

📊 Comparison with Base Branch

Metric Change
Total Tests 📉 -82290
Passed Tests 📉 -77886
Failed Tests —
Skipped Tests 📉 -4404

All tests are passing successfully.

✅ Patch coverage is 90.07%. Project has 2074 uncovered lines.
❌ Project coverage is 90.07%. Comparing base (7cb8094) to head (c27c69a).

Coverage diff
@@            Coverage Diff             @@
##        master       #PR       +/-##
==========================================
- Coverage    90.29%    90.07%    -0.22%
==========================================
  Files          202       193        -9
  Lines        26625     20886     -5739
  Branches      9926      7206     -2720
==========================================
+ Hits         24039     18812     -5227
- Misses        2586      2074      -512
- Partials      1515      1237      -278

Generated by Codecov Action

@github-actions

github-actions Bot commented Mar 19, 2026 •

Copy link
Copy Markdown
Contributor

Semver Impact of This PR

⚪ None (no version bump detected)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • (logging) Separate ignore lists for events/breadcrumbs and sentry logs by sl0thentr0py in #5698

Bug Fixes 🐛

Anthropic

  • Set exception info on streaming span when applicable by alexander-alderman-webb in #5683
  • Patch AsyncStream.close() and AsyncMessageStream.close() to finish spans by alexander-alderman-webb in #5675
  • Patch Stream.close() and MessageStream.close() to finish spans by alexander-alderman-webb in #5674

Documentation 📚

  • Add note on AI PRs to CONTRIBUTING.md by sentrivana in #5696

Internal Changes 🔧

  • Add -latest alias for each integration test suite by sentrivana in #5706
  • Use date-based branch names for toxgen PRs by sentrivana in #5704
  • 🤖 Update test matrix with new releases (03/19) by github-actions in #5703
  • Add client report tests for span streaming by sentrivana in #5677

Other

  • Next Python SDK major by sentrivana in #5005
  • Update CHANGELOG.md by sentrivana in #5685

🤖 This preview updates automatically when you update the PR.

Comment thread sentry_sdk/consts.py
Comment thread sentry_sdk/integrations/otlp.py
Comment thread tests/integrations/django/asgi/test_asgi.py
Comment thread sentry_sdk/integrations/otlp.py
Comment thread tests/integrations/threading/test_threading.py Outdated
Comment thread sentry_sdk/integrations/launchdarkly.py Outdated
Comment thread sentry_sdk/scope.py Outdated
Comment thread sentry_sdk/integrations/trytond.py
Comment thread sentry_sdk/integrations/chalice.py
Comment thread sentry_sdk/integrations/openai_agents/__init__.py
Comment thread sentry_sdk/integrations/pydantic_ai/__init__.py
Comment thread sentry_sdk/integrations/spark/spark_driver.py
Comment thread tests/integrations/launchdarkly/test_launchdarkly.py
Comment thread sentry_sdk/integrations/__init__.py Outdated
Comment thread sentry_sdk/integrations/openai_agents/__init__.py
Comment thread sentry_sdk/integrations/starlette.py
sentrivana added a commit that referenced this pull request Aug 6, 2026
Fixes for things that the bots
[surfaced](#5005) on the
major branch:
- some version checks were too late (after patching)
- fix TrytondWSGI integration name/`_MIN_VERSIONS` entry mismatch

Also, changed the warning of the `DidNotEnable` message from "X not
installed" to "X not installed or incompatible".
Comment thread sentry_sdk/integrations/redis/modules/queries.py
Comment thread sentry_sdk/integrations/langchain.py
Comment thread sentry_sdk/spotlight.py
Comment thread sentry_sdk/spotlight.py
Comment thread sentry_sdk/integrations/otlp.py
Comment thread sentry_sdk/integrations/aiomysql.py
Comment thread sentry_sdk/integrations/aiomysql.py Outdated
Comment thread MIGRATION_GUIDE.md Outdated
Comment thread sentry_sdk/integrations/otlp.py
Comment thread sentry_sdk/integrations/pyramid.py Outdated
Comment thread sentry_sdk/integrations/strawberry.py Outdated
Comment thread tests/integrations/aiomysql/test_aiomysql.py
Comment thread tests/integrations/aiomysql/test_aiomysql.py
Comment thread tests/integrations/aiomysql/test_aiomysql.py
Comment thread sentry_sdk/integrations/stdlib.py Outdated
Comment thread sentry_sdk/integrations/stdlib.py Outdated
Comment thread sentry_sdk/integrations/__init__.py
Comment thread sentry_sdk/integrations/threading.py
Comment thread tests/integrations/bottle/test_bottle.py
Comment thread tests/integrations/httpx/test_httpx.py Outdated
sentrivana added a commit that referenced this pull request Aug 26, 2026
Originally raised by a bot
[here](#5005 (comment)):
the `parse_version` function parses version strings as is (e.g. 3.1
becomes `(3, 1)`). We use these parsed version tuples in integrations to
compare the installed version against the minimum (defined in
`integrations/__init__.py`). The minimum versions are often three-part,
e.g. `(3, 1, 0)`.

This means that we can mistakenly consider a valid version to be below
the minimum, because in pure tuple terms, `(3, 1) < (3, 1, 0)` is true.

This can also happen in reverse (package version has three parts, while
our min version boundary has two).

In this PR, we make the internal version comparison work as expected
regardless of mismatches in the length of the version strings/tuples.
Comment thread sentry_sdk/integrations/typer.py
Comment thread sentry_sdk/_init_implementation.py
Comment thread sentry_sdk/integrations/arq.py Outdated
Comment thread sentry_sdk/integrations/asgi.py Outdated
Comment thread sentry_sdk/integrations/strawberry.py Outdated
Comment thread sentry_sdk/integrations/redis/redis.py
Comment thread sentry_sdk/integrations/stdlib.py Outdated
Comment thread MIGRATION_GUIDE.md Outdated
Comment thread MIGRATION_GUIDE.md Outdated
Comment thread sentry_sdk/consts.py
sentrivana and others added 26 commits October 7, 2026 09:56
Stop setting `mcp.tool.result.content_count` since the length can be determined from the value of the `gen_ai.tool.call.result` attribute.

See deprecation in getsentry/sentry-conventions@b06e641.
- added subsections for integrations, api changes, etc.
- added a small span streaming migration guide
#7896)

Refs PY-2936

---

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
…undant empty `data_collection` from sentry_init (#7907)

Refs PY-2936
…d, requests, rq, starlette, starlite, stdlib, strawberry, wsgi): Remove redundant empty `data_collection` from sentry_init (#7908)

Refs PY-2936

---------

Co-authored-by: Pablo Deputter <71842639+pabloDeputter@users.noreply.github.com>
…n section (#7893)

Fixes PY-2800
Fixes #7568

---------

Co-authored-by: Ivana Kellyer <ivana.kellyer@sentry.io>
### Description
Adds the following request/response attributes: `aws.s3.bucket`,
`aws.s3.key`, `aws.s3.upload_id`, `aws.s3.copy_source`, `aws.s3.delete`,
`aws.s3.part_number`, `http.response.body.size` (number of bytes in
payload), `file.size` (represents the total file/object size)

Following tests were removed/moved from old `test_s3.py` (old boto3
integration only added s3 streaming responses support):
- `test_basic()` removed - basically only tests botocore xml parsing
behavior; generic span behavior is already tested in `test_client.py`.
- `test_streaming()` / `test_streaming_close()` removed - behavior is
already tested in `test_client.py` with mock HTTP server.
- `test_span_origin()` removed - overlaps with existing tests.
- `test_omit_url_data_if_parsing_fails()` moved to `test_client.py` and
simplified since it's generic instrumentation.
- `test_breadcrumb()` and `test_url_query_data_collection_breadcrumb()`
moved and combined to `test_client.py`, once again since it's generic
instrumentation.

#### Issues
Resolves #7576
)

Group the TracingTestClass tracing tests into a class whose
setup/teardown restores the `static` and `class_` attributes that
`functions_to_trace` patches.

Resolves warnings like these
https://github.com/getsentry/sentry-python/actions/runs/37602693189/job/112730646420#step:6:3739

Refs PY-2637
Refs #6949
The task factory tests build the Sentry wrapper coroutine but never run
it, so it is garbage-collected unawaited. This surfaces as a "coroutine
was never awaited" RuntimeWarning attributed to an unrelated later test
(e.g. test_span_origin). Close the coroutine once the assertions are
done.

Fixes PY-2943
Fixes #7923
Comment thread MIGRATION_GUIDE.md
Comment on lines +232 to +235
"cookies": { "mode": "denylist", "terms": ["forwarded", "-ip", "remote-", "via", "-user"] },
"url_query_params": {
"mode": "denylist", "terms": ["forwarded", "-ip", "remote-", "via", "-user"],
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migration recipe leaves URL query parameters enabled

The migration recipe claims to roughly match send_default_pii=False, but configures url_query_params in denylist mode. That mode only redacts keys matching built-in or configured terms; other query keys and their values are retained and attached to request data or span URL attributes. Set url_query_params to {"mode": "off"} to preserve the previous opt-out from query-string collection.

Evidence
  • The migration recipe configures url_query_params with mode: denylist and terms that do not match ordinary keys such as email or search (MIGRATION_GUIDE.md).
  • _apply_key_value_collection_filtering preserves values for keys that do not match the sensitive denylist or configured terms (sentry_sdk/data_collection.py).
  • ASGI request extraction attaches a nonempty filtered query string, and URL attributes include it in http.query/url.full (sentry_sdk/integrations/_asgi_common.py).
  • The changelog records that query strings were gated behind send_default_pii in multiple integrations (CHANGELOG.md).

Identified by Warden · code-review · ZG6-PV7

Comment thread sentry_sdk/_compat.py
Comment on lines 63 to 84
if lazy_mode and not threads_enabled:
from warnings import warn

warn(
Warning(
"IMPORTANT: "
"We detected the use of uWSGI without thread support. "
"This might lead to unexpected issues. "
'Please run uWSGI with "--enable-threads" for full support.'
)
from sentry_sdk.utils import logger

logger.warning(
"IMPORTANT: "
"We detected the use of uWSGI without thread support. "
"This might lead to unexpected issues. "
'Please run uWSGI with "--enable-threads" for full support.'
)

return False

elif not lazy_mode and (not threads_enabled or not fork_hooks_on):
from warnings import warn

warn(
Warning(
"IMPORTANT: "
"We detected the use of uWSGI in preforking mode without "
"thread support. This might lead to crashing workers. "
'Please run uWSGI with both "--enable-threads" and '
'"--py-call-uwsgi-fork-hooks" for full support.'
)
from sentry_sdk.utils import logger

logger.warning(
"IMPORTANT: "
"We detected the use of uWSGI in preforking mode without "
"thread support. This might lead to crashing workers. "
'Please run uWSGI with both "--enable-threads" and '
'"--py-call-uwsgi-fork-hooks" for full support.'
)

@sentry-warden sentry-warden Bot Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uWSGI support warnings are suppressed during fresh client initialization

Both uWSGI misconfiguration messages use logger.warning, which is filtered unless initialization debug mode is active or the current client has debug=True. Client._init_impl() restores the initialization debug flag before calling check_uwsgi_thread_support(), and init() attaches the new client to the scope only after its constructor returns. Consequently, during fresh initialization, the warnings are suppressed even for init(debug=True), hiding guidance for configurations that may cause worker crashes. Consider preserving the always-visible warnings.warn behavior or otherwise allowing these warnings through the filter.

Evidence
  • check_uwsgi_thread_support() in sentry_sdk/_compat.py uses logger.warning for both missing uWSGI thread support and missing prefork fork hooks.
  • configure_logger() attaches _DebugFilter to that logger; the filter passes records only when _client_init_debug is true or the current client's debug option is true.
  • Client._init_impl() restores _client_init_debug in its finally block before calling check_uwsgi_thread_support(); _init_implementation._init() attaches the new client to the global scope only after the constructor returns.
  • Thus, on fresh initialization, even init(debug=True) reaches the check with the default non-recording client as the current client, so the warnings are filtered. An already-active debug client can change this outcome.

Identified by Warden · code-review, find-bugs · HLH-Y3D

Comment on lines +34 to +40
if getattr(exc, "_handled_by_sentry", False):
logger.info("DedupeIntegration dropped duplicated error event %s", exc)
return None

# we can only weakref non builtin types
try:
integration._last_seen.set(weakref.ref(exc))
except TypeError:
integration._last_seen.set(exc)

return event

@staticmethod
def reset_last_seen() -> None:
integration = sentry_sdk.get_client().get_integration(DedupeIntegration)
if integration is None:
return

integration._last_seen.set(None)
else:
with capture_internal_exceptions():
exc._handled_by_sentry = True
return event

@sentry-warden sentry-warden Bot Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dedupe suppresses a repeated exception after before_send drops it

The dedupe processor marks an exception as handled before before_send runs. If before_send drops the event, the marker remains; capturing that same exception instance again causes the processor to drop it as a duplicate. Reset the marker when the event is dropped, or mark the exception only after the event is accepted.

Evidence
  • DedupeIntegration.processor() sets exc._handled_by_sentry = True for an exception hint before returning the event.
  • Client._prepare_event() runs scope event processors before calling before_send; when that callback returns None, it records the loss but does not clear the exception marker.
  • A later capture_exception() of the same instance passes it in hint["exc_info"], so the dedupe processor sees the marker and returns None.
  • The existing dropped-exception test creates a new ValueError on each iteration, so it does not cover recapturing the same instance.
Also found at 1 additional location
  • sentry_sdk/client.py:558-566

Identified by Warden · find-bugs, code-review · FQ4-JUB

Comment on lines +134 to 155
with sentry_sdk.start_span(
name=function_name,
parent_span=None,
attributes={
"sentry.op": OP.FUNCTION_GCP,
"sentry.origin": GcpIntegration.origin,
"sentry.segment.name.source": SegmentNameSource.COMPONENT,
"cloud.provider": CLOUD_PROVIDER.GCP,
"faas.name": function_name,
**header_attributes,
**additional_attributes,
},
):
try:
return func(functionhandler, gcp_event, *args, **kwargs)
except Exception:
exc_info = sys.exc_info()
sentry_event, hint = event_from_exception(
exc_info,
client_options=client.options,
mechanism={"type": "gcp", "handled": False},
)
sentry_sdk.capture_event(sentry_event, hint=hint)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GCP flush runs before the invocation span is queued

client.flush() runs in the finally inside the start_span context. With streamed spans, the segment is queued only when that context exits, so this flush does not drain the current invocation's segment. Exiting the context signals the span batcher's asynchronous worker, but does not synchronously send the segment; if the GCP runtime suspends the process, delivery may be delayed until a later invocation or worker flush. Move the flush to a finally that runs after the span context exits.

Evidence
  • The GCP wrapper calls client.flush() in the inner finally, before the start_span context manager exits.
  • Span.__exit__() calls _end(), which captures the span into the scope; Client.flush() drains batchers, so the current segment is not in the batcher when this flush runs.
  • After the segment is queued, SpanBatcher.add() signals its daemon flusher asynchronously. This can send the segment later, but does not guarantee delivery before a GCP invocation is suspended.

Identified by Warden · find-bugs · TXW-7EW

Comment thread sentry_sdk/integrations/litellm.py
#7930)

### Description
Expand boto3 instrumentation with resource identity attributes; new
service-extensions (and tests) are added as well (mostly boilerplate,
but will be filled in the future).

- add `cloud.account.id` and `cloud.resource_id`
(https://opentelemetry.io/docs/specs/semconv/resource/cloud/) attributes
whenever a request contains a valid AWS ARN; these are extracted using
the common helper `_get_aws_arn_attributes()` in `boto3/_utils.py`; the
resource's AWS account is used here, not the caller's. It accepts
multiple request params. since operations may expose the same resource
under different names (the first matching ARN is used).
- additionally, following service extensions are registered: DynamoDB,
Kinesis, Lambda (file is called `lambda_.py` since it clashes with
`lambda` keyword, open for suggestions on renaming it), Secrets Manager,
SNS, SQS, and Step Functions.
- for SQS, `cloud.account.id` is extracted from `QueueURL` when no ARN
is available, e.g.
`https://sqs.us-east-2.amazonaws.com/123456789012/MyQueue`
(https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-queue-message-identifiers.html)

#### Issues
Resolves #7929
Comment thread MIGRATION_GUIDE.md
Comment on lines +232 to +234
"cookies": { "mode": "denylist", "terms": ["forwarded", "-ip", "remote-", "via", "-user"] },
"url_query_params": {
"mode": "denylist", "terms": ["forwarded", "-ip", "remote-", "via", "-user"],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cookie denylist in migration sample still sends ordinary cookie values

The example is presented as roughly matching send_default_pii=False, but its cookie denylist only redacts keys matching the listed terms or the built-in sensitive-key list. Ordinary cookie values such as theme and lang are still attached to events. Use "cookies": {"mode": "off"} (or an appropriate allowlist) if the goal is not to collect those cookies.

Evidence
  • MIGRATION_GUIDE.md presents the configuration as roughly matching send_default_pii=False, but configures cookies with a denylist of header-oriented terms.
  • data_collection._apply_key_value_collection_filtering() preserves unmatched key/value pairs in denylist mode; mode off returns an empty mapping.
  • Request extractors attach nonempty filtered cookies, and test_cookie_data_collection confirms theme and lang survive denylist mode while mode: off omits cookies.

Identified by Warden · find-bugs · JSW-CZZ

Comment on lines +61 to +62
if sentry_sdk.get_current_span() is None:
return original_method(*args, **kwargs)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensure_integration_enabled fallback calls cache method with wrong args

@ensure_integration_enabled(..., original_method) wraps _instrument_call, whose signature differs from the cache method; if DjangoIntegration is removed after patching, the fallback calls original_method(cache, method_name, original_method, args, kwargs, address, port) and raises TypeError. Decorate sentry_method instead, or fall back with original_method(*args, **kwargs).

Evidence
  • _instrument_call is decorated with @ensure_integration_enabled(DjangoIntegration, original_method) (line 44) and is invoked as _instrument_call(cache, method_name, original_method, args, kwargs, address, port).
  • ensure_integration_enabled on disable does return original_function(*args, **kwargs) with those same seven arguments.
  • Bound cache methods like get(key, default=None, version=None) cannot accept that signature, so cache ops fail after a re-init without DjangoIntegration.
  • The in-body early return correctly uses original_method(*args, **kwargs), showing the intended call shape the decorator fallback does not use.

Identified by Warden · find-bugs · B5H-HJS

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants